/*
 *                  BioJava development code
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  If you do not have a copy,
 * see:
 *
 *      http://www.gnu.org/copyleft/lesser.html
 *
 * Copyright for this code is held jointly by the individual
 * authors.  These should be listed in @author doc comments.
 *
 * For more information on the BioJava project and its aims,
 * or to join the biojava-l mailing list, visit the home page
 * at:
 *
 *      http://www.biojava.org/
 * 
 * Created on Dec 8, 2006
 * 
 */

package org.biodas.jdas.dassources.utils;

import java.util.ArrayList;
import java.util.List;


import org.biodas.jdas.schema.sources.CAPABILITY;
import org.biodas.jdas.schema.sources.COORDINATES;
import org.biodas.jdas.schema.sources.PROP;
import org.biodas.jdas.schema.sources.SOURCE;
import org.biodas.jdas.schema.sources.VERSION;
import org.apache.log4j.Logger;


/**
 * a class to filter a set of DAS sources, or to check if single DAS sources
 * fulfill certain requirements, pass in lists now rather than single values so
 * we can pull back all SOURCES with features or types capabilities if specified
 * as in the list of caps to check for
 * 
 * 
 * @author J Warren
 * 
 */
public class SourcesFilter {

	static Logger logger=Logger.getLogger(SourcesFilter.class);
	/**
	 * filter a set of DasSources by particular requirements all arguments can
	 * be set to null which means they are ignored
	 * 
	 * @param sources
	 * @param label
	 * @param titles 
	 * @param organism
	 * @param authority
	 * @param capability
	 * @param type
	 * @param spec
	 *            
	 * @param version
	 *            
	 * @return an array of DasSources that match the requested filtering rules
	 */
	public List<SOURCE> filterBy(List<SOURCE> sources, List<String> label,
			List<String> organisms, List<String> authorities,
			List<String> capabilities, List<String> validCapability,
			List<String> types, List<String> specs, List<String> versions,
			List<String> coordId, List<String> serverRootUrls, String active) {
		logger.debug("version 5 of filter");

		// cant return sources here as it then gets re-ordered so we need a copy
		// if ((label == null || label.size()==0) && (organisms ==
		// null||organisms.size()==0) && (authorities == null ||
		// authorities.size()==0)
		// && (capability == null || capability.size()==0) && (types == null ||
		// types.size()==0) && (specs==null || specs.size()==0)){
		// System.out.println("returning sources");
		// return sources;
		// }

		List<SOURCE> lst = new ArrayList<SOURCE>();
		          for (int i = 0; i < sources.size(); i++) {
                SOURCE source = sources.get(i);
                // if(source.getNickname().equals("Bamtestagain")){
                // test for correct label
                
                //yes I know I could just put && and have all this in one statement - but it's easier to debug the filter this way!
                if (!hasLabel(source, label)) {
                    logger.debug("fail on label");
                    continue;
                }
                if (!hasOrganism(source, organisms)) {
                     logger.debug("fail on org");
                    continue;
                }
                if (!hasAuthority(source, authorities)) {
                     logger.debug("fail on auth");
                   continue;
                }
                if (!hasCapability(source, capabilities)) {
                     logger.debug("fail on cap");
                    continue;
                }
                if (!hasValidCapability(source, validCapability)) {
                     logger.debug("fail on validCap");
                    continue;
                }
                if (!hasType(source, types)) {
                     logger.debug("fail on types");
                    continue;
                }
                if (!isSpec(source, specs)) {
                     logger.debug("fail on specs");
                    continue;
                }
                if (!hasVersion(source, versions)) {
                     logger.debug("fail on version");
                    continue;
                }
                if (!hasCoordinateId(source, coordId)) {
                     logger.debug("fail on coordid");
                    continue;
                }
                if (!hasServerRootUrl(source, serverRootUrls)) {
                     logger.debug("fail on serverroot");
                   continue;
                }
                if (!active(source, active)) {
                     logger.debug("fail on active");
                    continue;
                }
                     logger.debug("adding source in filter "+source.getUri());
                    lst.add(source);
                    
                }
                
		return lst;

	}

	public boolean active(SOURCE source, String active) {
		if(active==null||active.equals("any")){
			return true;
		}
		if(!active.equals("true")&&!active.equals("false")){
			return true;
		}
			boolean isActive=true;
			for (PROP prop : source.getVERSION().get(0).getPROP()) {
				if (prop.getName().equals("inactive")) {
					isActive=false;
				}
			}
			if(active.equals("true")&& isActive){
				return true;
			}
			if(active.equals("false")&&!isActive){
				return true;
			}
		
		return false;
	}

	/**
	 * Will return true if has any of the authorities.
	 * 
	 * @param source
	 * @param authorities
	 * @return
	 */
	public boolean hasAuthority(SOURCE source, List<String> authorities) {
		if (authorities == null || authorities.size() == 0)
			return true;
		List<COORDINATES> coords = new ArrayList<COORDINATES>();
		List<VERSION> versions = source.getVERSION();
		for (VERSION version : versions) {
			
				coords = version.getCOORDINATES();
			
			for (COORDINATES coord : coords) {
				for (String auth : authorities) {
					if(isAny(auth))return true;
					if (auth.equalsIgnoreCase(coord.getAuthority())) {
						return true;
					}
				}
			}
		}

		return false;
	}

	/**
	 * 
	 * @param source
	 * @param capabilities
	 *            - checks the capability e.g. features, but checks against the
	 *            capability type which is das1:features but parameter should be
	 *            features , we don't need to use the das1:
	 * @return
	 */

	public boolean hasCapability(SOURCE source, List<String> capabilities) {
		if (capabilities == null || capabilities.size() == 0)
			return true;
		
		
		List<VERSION> versions=source.getVERSION();
		for (VERSION version : versions) {

			List<CAPABILITY> caps = version.getCAPABILITY();

			for (CAPABILITY cap : caps) {
				//System.out.println("type=" + cap.getType());
				for (String capab : capabilities) {
					if(isAny(capab))return true;
					String[] array = cap.getType().split(":");
					if (array.length > 1) {
						//System.out.println("type after colon=" + array[1]);
						if ((capab).equalsIgnoreCase(array[1])) {
							return true;
						}
					} else {
						// if no : colon then try just checking for the
						// capability without the das1: prefix
						if ((capab).equalsIgnoreCase(cap.getType())) {
							return true;
						}
					}
				}
			}

		}
		return false;
	}

	/**
	 * Looks at the PROPERTY where it's valid or probably valid
	 * 
	 * @param source
	 * @param capabilities
	 *            e.g. features, sources can use Capabilities.FEATURES.getName()
	 *            to get exact strings
	 * @return
	 */
	public boolean hasValidCapability(SOURCE source, List<String> capabilities) {
		if (capabilities == null || capabilities.size() == 0)return true;
		
		List<VERSION> versions = source.getVERSION();
		for (VERSION version : versions) {

			List<PROP> props = version.getPROP();

			for (PROP prop : props) {
				// System.out.println("name" + prop.getName());
				// System.out.println("value" + prop.getValue());
				if (prop.getValue().equalsIgnoreCase("valid")
						|| prop.getValue().equalsIgnoreCase("probably_valid")) {
					for (String cap : capabilities) {
						// System.out.println("valid cap=" + cap);
						if(isAny(cap))return true;

						if (prop.getName().equals(cap)) {
							return true;
						}
					}
				}

			}
		}
		return false;
	}

	public boolean hasLabel(SOURCE source, List<String> labels) {
		if (labels == null || labels.size() == 0)
			return true;
		
		List<VERSION> versions = source.getVERSION();
		for (VERSION version : versions) {

			List<PROP> props = version.getPROP();

			for (PROP prop : props) {
				// System.out.println("name" + prop.getName());
				// System.out.println("value" + prop.getValue());
				if (prop.getName().equalsIgnoreCase("label")) {
					for (String label : labels) {
						if(isAny(label))return true;
						if (prop.getValue().equalsIgnoreCase(label)) {
							return true;
						}
					}
				}
			}

		}
		return false;
	}

	public boolean hasType(SOURCE source, List<String> types) {
		if (types == null || types.size() == 0)
			return true;

		List<COORDINATES> coords = null;
		
		List<VERSION> versions = source.getVERSION();
		for (VERSION version : versions) {
			
				coords = version.getCOORDINATES();
			
			for(COORDINATES coord:coords)
			for (String type : types) {
				if(isAny(type))return true;
				if (type.equalsIgnoreCase(coord.getSource())) {
					return true;
				}
			}
		}

		return false;

	}

	public boolean hasOrganism(SOURCE source, List<String> organisms) {
		// test for correct organism
		if (organisms == null || organisms.size() == 0){
			return true;
		}

		List<COORDINATES> coords = new ArrayList<COORDINATES>();
		
		List<VERSION> versions = source.getVERSION();
		for (VERSION version : versions) {
			
				coords = version.getCOORDINATES();
				for (COORDINATES coord : coords) {
					
					String[] chunks = coord.getContent().split(",");
					if (chunks.length > 2) {
						String coordOrganism = chunks[2];
						logger.debug("organism="+coordOrganism);
						for (String org : organisms) {
							if(isAny(org))return true;
							if (coordOrganism.equals(org)||coord.getTaxid().equals(org)) {
								return true;
							}
							// else if(coord.getTaxid().equals(organism))
						}
					}else if(chunks.length==2){
                                            //any organism means no organism specified so only 2 strings Authority and Type e.g. Uniprot, Protein Sequence
                                            //so it will be a match
                                            return true;
                                        }
				}

			

		}
		return false;
	}

	/**
	 * 
	 * @param source
	 * @param specs
	 * @return
	 */
	public boolean isSpec(SOURCE source, List<String> specs) {
		if (specs == null || specs.size() == 0)
			return true;
		
		
		for (String spec : specs) {
			if(isAny(spec))return true;
			DasSpec regSpec = DasSpec.convertToRegistrySpecification(spec);
			// System.out.println("spec="+regSpec.toString());
			
			List<VERSION> versions = source.getVERSION();
			for (VERSION versionA : versions) {
				for (PROP prop : versionA.getPROP()) {
					String name = prop.getName();
					String value = prop.getValue();
					 
					if (name.equals("spec")) {
						String convertedValue=(DasSpec.convertToRegistrySpecification(value)).toString();
						//System.out.println("cvProp="+convertedValue);
						//System.out.println("cvSpecParam="+regSpec.toString());
						if(convertedValue.equals(regSpec.toString())){
							
							return true;
						}
					}
				}
			}
		}

		// System.out.println("returning false:"+source.getUrl());
		return false;
	}

	/**
	 * Does this source have the specific coordinate version
	 * 
	 * @param source
	 * @param versions
	 *            coordinate version eg for NCBI_36 it's 36
	 * @return
	 */

	public boolean hasVersion(SOURCE source, List<String> cVersions) {
		if (cVersions == null || cVersions.size() == 0)
			return true;
		
		List<VERSION> versions = source.getVERSION();
		for (VERSION version : versions) {
			List<COORDINATES> coords = null;
			
				coords = version.getCOORDINATES();
			
			for (COORDINATES coord : coords) {
				String coordVersion = coord.getVersion();
				//System.out.println("coord version=" + coordVersion);
				for (String cversion : cVersions) {
					if(isAny(cversion))return true;
					if ((cversion.equalsIgnoreCase(coordVersion))) {
						return true;
					}
				}
			}
		}
		return false;
	}

	// private boolean isUCSC(SOURCE source) {
	// if (source.getId().startsWith("UCSC_"))
	// return true;
	// return false;
	// }

	public boolean hasCoordinateId(SOURCE source, List<String> ids) {
		if (ids == null || ids.size() == 0)
			return true;
		logger.debug(ids.toString());
		List<VERSION> version = source.getVERSION();
		for (VERSION v : version) {
			List<COORDINATES> coords = null;
		
				coords = v.getCOORDINATES();
			
			for (COORDINATES coord : coords) {
				String coordId = coord.getUri();
				// accept uris or ids
				logger.debug(coordId);
				for (String id : ids) {
					if(isAny(id))return true;
					if (!id.contains("/")) {// if the coordinate sytem of the
											// query is not a full uri then
											// assume it's the main DAS Registry
											// and chop off the domain name etc
											// from the coordinate uri to match
						coordId = coordId.substring(
								coordId.lastIndexOf("/") + 1, coordId.length());
						logger.debug("modified="+coordId);
					}
					if ((coordId.equalsIgnoreCase(id))) {
						return true;
					}
				}
			}
		}
		return false;
	}

	public boolean hasServerRootUrl(SOURCE source, List<String> serverUrls) {
		//changed the way this works as sourcesDocs aren't always the root url of the server now
		//System.out.println("altered filter");
		return this.hasSourcesDoc(source, serverUrls);
//		List<VERSION> versionAdapters = source.getVERSION();
//		for (VERSION v : versionAdapters) {
//			List<CAPABILITY> caps = v.getCAPABILITY();
//			if (caps.size() > 0) {
//				for (String serverUrl : serverUrls) {
//					if(isAny(serverUrl))return true;
//					if (serverUrl != null) {
//						if (!serverUrl.endsWith("/"))
//							serverUrl += "/";
//						if (caps.get(0).getQueryUri().startsWith(serverUrl)) {
//							return true;
//						}
//					}
//				}
//
//			}
//
//		}
//
//		return false;

	}
	
	private boolean hasSourcesDoc(SOURCE source, List<String> serverUrls) {

		if (serverUrls == null || serverUrls.size() == 0)
			return true;
		
		RegistrySourceAdapter ra=new RegistrySourceAdapter(source);
		
		for (String serverUrl : serverUrls) {
		if(isAny(serverUrl))return true;
		if (serverUrl != null) {
			if (!serverUrl.endsWith("/"))
				serverUrl += "/";
			if (ra.getSourcesDoc().equals(serverUrl)) {
				return true;
			}
		}
	}
		return false;
	}
	
	
	private boolean hasTitle(SOURCE source, List<String> title){
		for(String nickname:title){
			if(isAny(nickname))return true;
			if(source.getTitle().equals(nickname))return true;
		}
		return false;
	}
	
	private boolean isAny(String param){
		if((param==null)||param.equals("") || param.equals("any"))return true;
		return false;
	}
}
